# Copyright (c) 2021-2024 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

project(irtoc_backend)

###############################################################################
# irtoc: library
#
set(SOURCES
    compilation.cpp
    compiler/codegen_boundary.cpp
    compiler/codegen_fastpath.cpp
    compiler/dangling_pointers_checker.cpp
    function.cpp
    irtoc_options.cpp
)

if (PANDA_COMPILER_DEBUG_INFO)
    list(APPEND SOURCES dwarf_builder.cpp)
endif()

panda_add_library(irtoc SHARED ${SOURCES})

if (PANDA_LLVM_IRTOC AND (PANDA_TARGET_AMD64 OR PANDA_TARGET_ARM64))
    panda_target_link_libraries(irtoc llvmbackend)
    panda_target_include_directories(irtoc PUBLIC ${CMAKE_SOURCE_DIR}/libllvmbackend)
endif()

panda_target_link_libraries(irtoc arkcompiler elfio arkbase)

add_dependencies(irtoc asm_defines_generator cross_values)

set_source_files_properties(function.cpp compilation.cpp dwarf_builder.cpp PROPERTIES COMPILE_FLAGS -Wno-shadow)

panda_target_include_directories(irtoc
    PUBLIC ${PANDA_BINARY_ROOT}/panda_gen_options
    PUBLIC ${PANDA_BINARY_ROOT}/cross_values
    PUBLIC ${PANDA_BINARY_ROOT}/runtime/include
    PUBLIC ${PANDA_ROOT}/../common_interfaces
)

panda_target_include_directories(irtoc
    SYSTEM PUBLIC ${PANDA_THIRD_PARTY_SOURCES_DIR}/elfio
    SYSTEM PUBLIC ${PANDA_THIRD_PARTY_SOURCES_DIR}/elfio/elfio
)

# Compile one or more irtoc script files into elf object file.
# Arguments:
#   TARGET_NAME - name of the target, that will generate object file. Thereby user can make dependency on it.
#   INPUT_FILES - input Irtoc scripts, usually it is files from `irtoc/scripts` folder.
#   TARGET_VARIABLE - name of a variable, which will hold name of the generated object file, so the user can link it to
#                     the target's sources.
#   TARGET_VARIABLE_LLVM - name of a variable, which will hold name of the object file generated by llvm, so the user
#                          can link it to the target's sources.
#   LLVM_BACKEND_INLINE_MODULE_FILES - a list of LLVM IR bitcode module files for inlining
#   SKIP_VALIDATION - skip validation process (necessary for LLVM FastPath compilation)
function(irtoc_compile)
    set(noValues)
    set(singleValueArgs TARGET_NAME TARGET_VARIABLE TARGET_VARIABLE_LLVM SKIP_VALIDATION)
    set(multiValueArgs INPUT_FILES LLVM_BACKEND_INLINE_MODULE_FILES)
    cmake_parse_arguments(ARG "${noValues}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN})

    set(BUILD_DIR "${IRTOC_BUILD_DIR}/${ARG_TARGET_NAME}")
    file(MAKE_DIRECTORY ${BUILD_DIR})

    set(IRTOC_GENERATOR_TARGET irtoc_generate_${ARG_TARGET_NAME})
    set(IRTOC_COMPILE_OUTPUT "${BUILD_DIR}/irtoc_code.cpp")
    if (PANDA_LLVM_INTERPRETER)
        list(APPEND IRTOC_COMPILE_OUTPUT "${BUILD_DIR}/irtoc_code_llvm.cpp")
    endif()
    irtoc_generate(
        TARGET ${IRTOC_GENERATOR_TARGET}
        IR_API ir-constructor
        INPUT_FILES "${ARG_INPUT_FILES}"
        OUTPUT_FILES "${IRTOC_COMPILE_OUTPUT}"
        WORKING_DIRECTORY ${BUILD_DIR}
    )

    ###############################################################################
    #  Executable
    #
    set(SOURCES ${IRTOC_COMPILE_OUTPUT} ${IRTOC_SOURCE_DIR}/backend/irtoc.cpp)

    set(IRTOC_EXEC ${ARG_TARGET_NAME}_exec)

    panda_add_executable(${IRTOC_EXEC} ${SOURCES})
    add_dependencies(${IRTOC_EXEC} ${IRTOC_GENERATOR_TARGET})

    panda_target_link_libraries(${IRTOC_EXEC} irtoc)

    panda_add_sanitizers(TARGET ${IRTOC_EXEC} SANITIZERS ${PANDA_SANITIZERS_LIST})

    ###############################################################################
    #  final target: run irtoc tool, that will generate resulting object file
    #
    set(IRTOC_OUTPUT ${BUILD_DIR}/${ARG_TARGET_NAME}.o)
    if (PANDA_LLVM_IRTOC)
        set(IRTOC_OUTPUT_LLVM ${BUILD_DIR}/${ARG_TARGET_NAME}_llvm.o)
    endif()

    # Irtoc validation is enabled only in pure Release build
    if (CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT IRTOC_WITH_SANITIZERS)
        if (NOT DEFINED ARG_SKIP_VALIDATION OR NOT ARG_SKIP_VALIDATION)
            set(VALIDATION_COMMAND ruby ${IRTOC_SOURCE_DIR}/lang/validation.rb ${BUILD_DIR}/validation.yaml ${BUILD_DIR}/disasm.txt)
        endif()
    endif()

    set(command_comment "Building ${IRTOC_OUTPUT}")
    if (PANDA_LLVM_IRTOC)
        STRING(APPEND command_comment ", ${IRTOC_OUTPUT_LLVM}")
        set(IRTOC_OUTPUT_LLVM_OPTION "--irtoc-output-llvm=${IRTOC_OUTPUT_LLVM}")
    endif()

    set(llvm_inline_module "")
    set(llvm_inline_module_bc "")
    if (NOT "${ARG_LLVM_BACKEND_INLINE_MODULE_FILES}" STREQUAL "")
        set(llvm_inline_module_bc "${CMAKE_BINARY_DIR}/irtoc/irtoc_interpreter/interpreter_inline.bc")
        if (NOT LLVM_LINK)
            message(FATAL_ERROR "LLVM_LINK is required but was not set")
        endif()
        if(PANDA_BUILD_LLVM_BINARIES)
            # LLVM_LINK is set, but binary might not be build yet 
            set(llvm-link-dependency x86_64-llvm-link)
        endif()
        add_custom_command(
                OUTPUT ${llvm_inline_module_bc}
                COMMAND ${LLVM_LINK} ${ARG_LLVM_BACKEND_INLINE_MODULE_FILES} -o ${llvm_inline_module_bc}
                COMMAND_EXPAND_LISTS
                COMMENT "Linking ${ARG_OUTPUT}"
                DEPENDS ${ARG_LLVM_BACKEND_INLINE_MODULE_FILES} irtoc_int_llvm_inl_module ${llvm-link-dependency}
        )
        set(command_comment "Building ${IRTOC_OUTPUT}")
        string(APPEND command_comment ", ${IRTOC_OUTPUT_LLVM}")
        set(IRTOC_OUTPUT_LLVM_OPTION "--irtoc-output-llvm=${IRTOC_OUTPUT_LLVM}")
        if (PANDA_LLVM_INTERPRETER_INLINING)
            set(llvm_inline_module "--llvm-inline-module=${llvm_inline_module_bc}")
        endif()
    endif()

    set(compiler_cpu_features "")
    if (NOT "${PANDA_TARGET_CPU_FEATURES}" STREQUAL "")
        set(compiler_cpu_features "--compiler-cpu-features=${PANDA_TARGET_CPU_FEATURES}")
    endif()

    add_custom_command(OUTPUT ${IRTOC_OUTPUT} ${IRTOC_OUTPUT_LLVM}
        COMMENT             ${command_comment}
        COMMAND             touch disasm.txt
        COMMAND             ${PANDA_RUN_PREFIX} ${IRTOC_EXEC}
                                --irtoc-output ${IRTOC_OUTPUT}
                                --compiler-cross-arch ${IRTOC_TARGET}
                                ${compiler_cpu_features}
                                ${IRTOC_OUTPUT_LLVM_OPTION}
                                --compiler-disasm-dump:single-file
                                ${llvm_inline_module}
        COMMAND             ${VALIDATION_COMMAND}
        WORKING_DIRECTORY   ${BUILD_DIR}
        DEPENDS             ${IRTOC_EXEC} ${llvm_inline_module_bc}
    )

    add_custom_target(${ARG_TARGET_NAME} ALL
        DEPENDS ${IRTOC_OUTPUT} irtoc_plugins_txt ${llvm_inline_module_bc}
    )
    add_dependencies(${ARG_TARGET_NAME} ${IRTOC_EXEC})

    # Set output variables to the generated object files, so caller can link it to target's sources
    set(${ARG_TARGET_VARIABLE} ${IRTOC_OUTPUT} PARENT_SCOPE)
    set(${ARG_TARGET_VARIABLE_LLVM} ${IRTOC_OUTPUT_LLVM} PARENT_SCOPE)
endfunction()

###############################################################################
#  Other stuff
#
panda_gen_options(TARGET irtoc YAML_FILE options.yaml GENERATED_HEADER irtoc_options_gen.h)

panda_add_sanitizers(TARGET irtoc SANITIZERS ${PANDA_SANITIZERS_LIST})

###############################################################################
#  Tests
#
set(PANDA_IRTOC_TESTS_SOURCES
    ${PANDA_ROOT}/compiler/tests/unit_test.cpp
)

if(PANDA_TARGET_AMD64 OR PANDA_TARGET_ARM64)
    list(APPEND PANDA_IRTOC_TESTS_SOURCES
        compiler/tests/dangling_pointers_checker_test.cpp
    )
endif()

set(PANDA_IRTOC_TESTS_LIBRARIES arkcompiler arkbase arkruntime arkassembler irtoc)
if (NOT (PANDA_TARGET_MOBILE OR PANDA_TARGET_OHOS OR PANDA_ENABLE_FUZZBENCH))
    list(APPEND PANDA_IRTOC_TESTS_LIBRARIES stdc++fs)
endif()

if(NOT PANDA_MINIMAL_VIXL AND PANDA_COMPILER_ENABLE AND PANDA_WITH_TESTS)
    panda_add_gtest(
        CONTAINS_MAIN
        NAME irtoc_compiler_unit_tests
        SOURCES
            ${PANDA_IRTOC_TESTS_SOURCES}
        INCLUDE_DIRS
            ${PANDA_ROOT}/assembler
        LIBRARIES
            ${PANDA_IRTOC_TESTS_LIBRARIES}
        SANITIZERS
            ${PANDA_SANITIZERS_LIST}
    )
endif()
